/**
 * Copyright (c) 2011 eelpo.com
 * All rights reserved.
 */
package com.eelpo.common.security;

/**
 * 功能描述：基于口令加密的PBE（Password-based-Encryption）对称算法PBEWithMD5AndDES
 * <p>
 * 版权所有：广州市艾尔博科技有限公司
 * <p>
 * 未经本公司许可，不得以任何方式复制或使用本程序任何部分
 * 
 * @author <a href="mailto:Brian@eelpo.com">汪洪州</a>
 * 
 * @since 1.0
 */
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.Key;
import java.security.SecureRandom;
import java.security.spec.KeySpec;

import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;

import com.eelpo.common.encoder.BASE64DecoderStream;
import com.eelpo.common.encoder.BASE64EncoderStream;
import com.eelpo.common.security.constant.Constants;
import com.eelpo.common.util.BlankUtils;
import com.eelpo.common.util.StringUtils;

public class PBEEncyrptor {

	// public static final Integer PBE_ITERATIONS = 1000;
	// public static final String PBEWithMD5AndDES = "PBEWithMD5AndDES";
	// public static final String PBEWithSHA1AndDESede = "PBEWithSHA1AndDESede";

	// 加密口令
	private String password;

	public PBEEncyrptor() {
	}

	public PBEEncyrptor(String password) {
		this.password = password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	public String getPassowrd() {
		return password;
	}

	/**
	 * 方法用途和描述：基于密码加密字符串
	 * 
	 * <p>
	 * 方法实现逻辑描述：将字符串转换成字节数组进行加密，然后基于64加码后返回密文
	 * </p>
	 * 
	 * @param plainText
	 *            :要加密的字符串
	 * @return 返回加密后的密文
	 */
	public String encrypt(String plainText) {

		if (BlankUtils.isBlank(plainText)) {
			return StringUtils.EMPTY;
		}

		byte[] salt = getSalt();

		// 执行加密返回密文数组
		byte[] cipherBytes = getCipherBytes(salt, plainText.getBytes());

		// 对密文和随机(盐)salt进行组合并返回
		return BASE64EncoderStream.encode(salt).concat(BASE64EncoderStream.encode(cipherBytes));

	}

	/**
	 * 方法用途和描述：基于密码加密文件
	 * 
	 * <p>
	 * 方法实现逻辑描述：随机盐和迭代次数两个参数构造PBEParameterSpec，
	 * 加密口令构造Key，使用key和PBEParameterSpec构造加密器进行加密
	 * </p>
	 * 
	 * @param inputPath
	 *            ：需要加密的文件路径
	 * @param outputPath
	 *            ：加密输出文件路径
	 */
	public void encrypt(String inputPath, String outputPath) {

		try {

			InputStream inputStream = new FileInputStream(inputPath);
			OutputStream outputStream = new FileOutputStream(outputPath);

			// 生成8字节的随机(盐)salt
			byte[] salt = getSalt();

			Cipher cipher = getCipher(Cipher.ENCRYPT_MODE, salt);

			CipherOutputStream cOutputStream = new CipherOutputStream(outputStream, cipher);

			// 先把随机(盐)salt写入文件再把加密内容写入文件
			outputStream.write(salt);

			for (int i = inputStream.read(); i != -1; i = inputStream.read())
				cOutputStream.write(i);

			inputStream.close();
			cOutputStream.close();
			outputStream.close();

		} catch (Exception e) {
			e.printStackTrace();
		}

	}

	/**
	 * 方法用途和描述：基于密码加密文节流
	 * 
	 * <p>
	 * 方法实现逻辑描述：通过随机盐构造加密器进行加密
	 * </p>
	 * 
	 * @param plainBytes
	 *            ：需要加密的字节数组
	 * @return 返回加密后的字节数组
	 */
	public byte[] encrypt(byte[] plainBytes) {

		if (BlankUtils.isBlank(plainBytes)) {
			return new byte[] {};
		}

		// 生成8字节的随机(盐)salt
		byte[] salt = getSalt();

		// 执行加密返回密文数组
		byte[] cipherBytes = getCipherBytes(salt, plainBytes);

		// 对密文和随机(盐)salt进行组合并返回
		byte[] saltWithCipherBytes = new byte[cipherBytes.length + 8];
		System.arraycopy(salt, 0, saltWithCipherBytes, 0, 8);
		System.arraycopy(cipherBytes, 0, saltWithCipherBytes, 8, saltWithCipherBytes.length - 8);
		return saltWithCipherBytes;

	}

	/**
	 * 方法用途和描述：基于密码解密字符串
	 * 
	 * <p>
	 * 方法实现逻辑描述：从字符串里分解随机盐和密文，通过随机盐构造解密器进行解密
	 * </p>
	 * 
	 * @param cipherText
	 *            ：随机盐和密文的组合字符串
	 * @return 返回明文字符串
	 */
	public String decrypt(String cipherText) {

		if (BlankUtils.isBlank(cipherText)) {
			return StringUtils.EMPTY;
		}

		try {

			// 获取盐(salt)和密文,salt占12个字符
			String saltString = cipherText.substring(0, 12);
			String cipherString = cipherText.substring(12);

			// 对盐(salt)和密文解码
			byte[] salt = BASE64DecoderStream.decode(saltString);
			byte[] cipherBytes = BASE64DecoderStream.decode(cipherString);

			// 执行解密返回明文信息
			return new String(getPlainBytes(salt, cipherBytes));

		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}

	}

	/**
	 * 方法用途和描述：基于密码解密文件
	 * 
	 * <p>
	 * 方法实现逻辑描述：随机盐和迭代次数两个参数构造PBEParameterSpec，
	 * 加密口令构造Key，使用key和PBEParameterSpec构造解密器进行解密
	 * </p>
	 * 
	 * @param inputPath
	 *            ：需要解密的文件路径
	 * @param outputPath
	 *            ：解密后输出文件路径
	 */
	public void decrypt(String inputPath, String outputPath) {

		try {

			FileInputStream inputStream = new FileInputStream(inputPath);
			FileOutputStream outputStream = new FileOutputStream(outputPath);

			byte[] salt = new byte[8];
			inputStream.read(salt, 0, 8);

			Cipher cipher = getCipher(Cipher.DECRYPT_MODE, salt);

			CipherInputStream cInputStream = new CipherInputStream(inputStream, cipher);

			for (int i = cInputStream.read(); i != -1; i = cInputStream.read())
				outputStream.write(i);

			inputStream.close();
			cInputStream.close();
			outputStream.close();

		} catch (Exception e) {
			e.printStackTrace();
		}

	}

	/**
	 * 方法用途和描述：基于密码解密字节数组
	 * 
	 * <p>
	 * 方法实现逻辑描述：通过随机盐构造解密器进行加密
	 * </p>
	 * 
	 * @param cipherBytes
	 *            ：随机盐和密文的组合字节数组
	 * @return 返回明文字节数组
	 */
	public byte[] decrypt(byte[] cipherBytes) {

		if (BlankUtils.isBlank(cipherBytes)) {
			return new byte[] {};
		}

		// 对随机(盐)salt+密文的组合进行分解
		byte[] salt = new byte[8];
		byte[] pCipherBytes = new byte[cipherBytes.length - 8];
		System.arraycopy(cipherBytes, 0, salt, 0, 8);
		System.arraycopy(cipherBytes, 8, pCipherBytes, 0, cipherBytes.length - 8);

		// 执行解密并返回
		return getPlainBytes(salt, pCipherBytes);

	}

	/*
	 * 方法用途和描述：核心加密方法，进行纯加密
	 * 
	 * <p> 方法实现逻辑描述：随机盐和迭代次数两个参数构造PBEParameterSpec，
	 * 加密口令构造Key，使用key和PBEParameterSpec构造加密器进行加密 </p>
	 * 
	 * @param salt：随机盐
	 * 
	 * @param plainBytes：明问字节猪组
	 * 
	 * @return 返回密文字节数组
	 */
	private byte[] getCipherBytes(byte[] salt, byte[] plainBytes) {

		try {
			// 获取加密器
			Cipher cipher = getCipher(Cipher.ENCRYPT_MODE, salt);

			// 对明文信息执行加密并返回
			return cipher.doFinal(plainBytes);

		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}

	}

	/*
	 * 方法用途和描述：生成8字节的随机(盐)salt
	 * 
	 * @return 返回8字节的随机(盐)salt
	 */
	private byte[] getSalt() {

		// 生成8字节的随机(盐)salt
		byte[] salt = new byte[8];
		SecureRandom random = new SecureRandom();
		random.nextBytes(salt);
		return salt;
	}

	/*
	 * 方法用途和描述：核心解密方法，进行纯解密
	 * 
	 * <p> 方法实现逻辑描述：随机盐和迭代次数两个参数构造PBEParameterSpec，
	 * 加密口令构造Key，使用key和PBEParameterSpec构造解密器进行解密 </p>
	 * 
	 * @param salt：随机（盐）
	 * 
	 * @param cipherBytes：密文字节数组 @return：返回明文信息
	 */
	private byte[] getPlainBytes(byte[] salt, byte[] cipherBytes) {

		try {

			// 获取解密器
			Cipher cipher = getCipher(Cipher.DECRYPT_MODE, salt);

			// 对密文执行解密并返回
			return cipher.doFinal(cipherBytes);

		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}

	}

	/*
	 * 方法用途和描述：获取加密解密器
	 * 
	 * @param mode:算法模式
	 * 
	 * @param salt:随机盐（salt）
	 * 
	 * @return 返回加密解密器对象
	 */
	private Cipher getCipher(int mode, byte[] salt) {

		try {

			// 生成密钥内容的规范PBEKeySpec
			KeySpec keySpec = new PBEKeySpec(password.toCharArray());
			Key key = KeyBuilder.getInstance().getSymmetryKey(Constants.PBEWITHSHA1ANDDESEDE, keySpec);

			// 构造一个PBE算法的两个参数，详见RSA的PKCS#5标准
			PBEParameterSpec parameterSpec = new PBEParameterSpec(salt, Constants.PBE_ITERATIONS);

			Cipher cipher = Cipher.getInstance(Constants.PBEWITHSHA1ANDDESEDE);
			// 初始化Cipher对象(解密模式-密钥-算法参数)
			cipher.init(mode, key, parameterSpec);
			return cipher;

		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}
}
